{This file is part of CheLive.

CheLive is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Foobar.  If not, see <http://www.gnu.org/licenses/>.}

{Реализует работу с таймингом}
unit CoreTimer;

interface

uses
{$IFDEF WIN32}
	Windows,
{$ENDIF}
{$IFDEF LINUX}
	libc,
{$ENDIF}
	CoreTypes;

type
	{Процедура-обработчик тика таймера @seealso(TTimerMan.Add)
	@param(param пользовательский параметр)}
	TTimerProc = procedure(param: Pointer);
	
	{Таймер
	@member(step кол-во миллисекунд между тиками)
	@member(targetTime время в которое должен произойти следующий тик)
	@member(proc адрес процедуры-обработчика тика)
	@member(param пользовательский параметр)}
	TTimer = class(TListItem)
		step, targetTime: Cardinal;
		proc: TTimerProc;
		param: Pointer;
	end;

	TTimers = specialize TList<TTimer>;

	{Менеджер таймеров
	@member(start время начала работы менеджера)
	@member(cur текущее время)
	@member(prev время пердидущей итерации)
	@member(dt разница текущего времени и времени предидущей итерации)
	@member(fps кол-во кадров в секунду)
	@member(fpsc кол-во отрисованных кадров (должно увеличиваться на единицу после отрисовка очередного кадра (пользователем), 
		сбрасывается каждую секунду))}
	TTimerMan = class(TTimers)
		start, cur, prev, dt: Cardinal;
		fps, fpsc: Integer;
		
		{Обновление состояния (если нужно - обработка событий таймеров)}
		procedure Update;
		{Текущее время
		@returns(время в миллисекундах (стартовая точка отсчета времени не определена в Linux))}
		function Time: Cardinal;
		{Добавление нового таймера
		@param(step кол-во миллисекунд между тиками)
		@param(proc адрес процедуры-обработчика тика)
		@param(param пользовательский параметр)
		@returns(созданный таймер)}
		function Add(step: Cardinal; proc: TTimerProc; param: Pointer = nil): TTimer;

		constructor Create;
	end;

var
	tim: TTimerMan;

implementation

uses
	SysUtils, CoreConsole;

procedure TimerFPS(param: Pointer);
begin
	tim.fps := tim.fpsc;
	tim.fpsc := 0;
end;

constructor TTimerMan.Create;
begin
	inherited;

	start := Time;
	cur := start;
	prev := start;

	Add(1000, @TimerFPS);
end;

procedure TTimerMan.Update;
var
	i: Integer;
begin
	cur := Time;
	
	for i := 0 to size - 1 do
	begin
		if (items[i] <> nil) and (items[i].targetTime <= cur) then
		begin
			items[i].targetTime := items[i].targetTime + items[i].step; //или "time + items[i].step;"?
			items[i].proc(items[i].param);
		end;
	end;

	dt := cur - prev;
	prev := cur;
end;

function TTimerMan.Time: Cardinal;
var
{$IFDEF WIN32}
	i, f: TLargeInteger;
{$ENDIF}
{$IFDEF LINUX}
	ts: timespec;
{$ENDIF}
begin
{$IFDEF WIN32}
	QueryPerformanceFrequency(f);
	QueryPerformanceCounter(i);
	Result := Round(i / f * 1000);
{$ENDIF}
{$IFDEF LINUX}
	clock_gettime(1{CLOCK_MONOTONIC}, @ts);
	
	Result := Round(ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
{$ENDIF}
end;

function TTimerMan.Add(step: Cardinal; proc: TTimerProc; param: Pointer = nil): TTimer;
var
	timer: TTimer;
begin
	timer := TTimer.Create;
	timer.step := step;
	timer.targetTime := Time + step;
	timer.proc := proc;
	timer.param := param;

	Result := inherited Add(timer);
end;

end.
